package threads;

import java.util.*;
import model.*;
import supermarket.*;

public class PurchaseThread extends Thread {

    private DBmanager db;
    private Customer customer;
    private Store store;
    private ArrayList<ProductPurchase> basket;
    private Purchase purchase;
    private ThreadManager tm;
    private boolean submitted;
    private CreditCardVerifier ccverifier;
    private boolean validCreditCard;
    // Αριθμός των προσπαθειών <4
    private int retries;
    private ArrayList<Calendar> retriesTimestamps;

    public PurchaseThread(DBmanager db, ThreadManager tm, Customer c) {
        this.db = db;
        this.tm = tm;
        this.customer = c;
        this.ccverifier = new CreditCardVerifier(db);
        this.retriesTimestamps = new ArrayList<Calendar>();
        this.validCreditCard = true;
    }

    public void populateBasketCart() {


        this.store = getRandomStore();

        synchronized (db) {
            // Δημιούργησε μια αγορά για αυτόν τον πελάτη σε αυτό το κατάστημα
            this.purchase = this.db.newPurchase(this.customer, this.store);

            //Συσχέτισε την αγορά με το κατάστημα και σώστο στην ΒΔ
            this.store.getPurchaseList().add(purchase);
            this.db.updateStore(store);

            //Συσχέτισε την αγορά με τον Πελάτη και σώστο στην ΒΔ
            this.customer.getPurchaseList().add(purchase);
            this.db.updateCustomer(customer);

            // Φτιάξε το καλάθι με αριθμό προιόντων <20 και ενημέρωσε την ΒΔ.
            this.basket = getRandomPurchaseList(this.store, this.purchase);

            // Βρές αριθμό προιόντων και σώστο την κλάση purchase
            this.purchase.setAmount(this.getNumOfProducts(this.basket));

            // υπολόγισε κερδιμένους πόντους και σώστο την κλάση purchase
            this.purchase.setPointsEarned(this.getPointsEarned(this.basket));

            // υπολόγισε delivery και σώστο την κλάση purchase
            this.purchase.setDelivery(this.getDelivery(customer));

            //Σώστα όλα στην Βάση
            this.db.updatePurchase(purchase);
        }
    }

    private void updateCustomerPointsAndChecks(Customer c, Purchase p) {
        int Existing_points = c.getAvailablePoints();
        int New_points = p.getPointsEarned();
        int points = Existing_points + New_points;

        if (points > 200) {
            // Δημιούργησε επιταγές και δώστου τους ρέστους πόντους
            while (points > 200) {
                points -= 200;
                Checks cc = this.db.newCheck();
                c.getChecksList().add(cc);
            }
            c.setAvailablePoints(points);
        } else {
            // Add points and updated customer
            c.setAvailablePoints(Existing_points + New_points);
        }
        // Ενημέρωσε την ΒΔ με τις αλλαγές στον πελάτη
        this.db.updateCustomer(c);
    }

    private int getBasketCost(ArrayList<ProductPurchase> b) {
        int cost = 0;
        for (int i = 0; i < b.size(); i++) {
            cost += b.get(i).getProductId().getPrice();
        }
        return cost;
    }

    private boolean getDelivery(Customer c) {

        String addr = c.getCustomerAddress();

        if (addr.isEmpty()) {
            return false;
        } else {
            return true;
        }
    }

    private int getPointsEarned(ArrayList<ProductPurchase> b) {
        int points = 0;
        for (int i = 0; i < b.size(); i++) {
            points += b.get(i).getProductId().getPoints();
        }
        return points;
    }

    private int getNumOfProducts(ArrayList<ProductPurchase> b) {
        int products = 0;
        for (int i = 0; i < b.size(); i++) {
            products += b.get(i).getQuantity();
        }
        return products;
    }

    private Customer getRandomCustomer() {
        Random r = new Random();
        Customer c;
        // Πάρε όλους τους πελάτες από την Βάση Δεδομένων
        List<Customer> customers = this.db.getAllCustomerList();
        // Επέλεξε τυχαία έναν πελάτη
        int customerIndex = r.nextInt(customers.size());
        c = customers.get(customerIndex);
        return c;
    }

    private Store getRandomStore() {
        Random r = new Random();
        // Διάλεξε ένα τυχαίο κατάστημα που έχει προιόντα
        List<Store> sl = this.db.getStoreList();
        boolean selected = false;
        Store s = null;
        List<Product> products = null;
        while (!selected) {
            int storeIndex = r.nextInt(sl.size());
            s = sl.get(storeIndex);
            // Πάρε την λίστα με τα προιόντα του
            products = new ArrayList<Product>(s.getProductList());
            if (products.size() > 0) {
                selected = true;
            }
        }
        return s;
    }

    private ArrayList<ProductPurchase> getRandomPurchaseList(Store s, Purchase purchaseID) {

        Random r = new Random();

        //Φτιάξε την λίστα του καλαθιού αγορών
        ArrayList<ProductPurchase> basket = new ArrayList<ProductPurchase>();

        //Πάρε την λίστα με τα προιόντα του καταστήματος
        List<Product> products = new ArrayList<Product>(s.getProductList());

        // Υπολόγισε τυχαίο αριθμό προιόντων που θα αγοραστούν <20
        int numOfProducts = 1 + r.nextInt(20);
        int totalamountSelected = 0;

        // Επέλεξε numOfProducts τυχαία προϊόντα
        for (int i = 0; i < numOfProducts; i++) {
            // Επέλεξε ένα τυχαίο προϊόν
            int productIndex = r.nextInt(products.size());
            // Ελεξε αν υπάρχου προιόντα αφου σε κάθε επανάληψη
            // αφαιρούμε και ένα
            if (productIndex != 0) {
                // βρες το προιόν και βγάλτο απο την λίστα
                // για να μήν το ξαναδιαλέξεις
                Product p = products.get(productIndex);
                products.remove(p);

                // Επέλεξε τυχαία ποσότητα 1-6(λέμε τώρα παραδοχή)
                int quantity = 1 + r.nextInt(6);
                // Ελεγξε μην υπερβείς το άνω όριο των numOfProducts
                if ((totalamountSelected + quantity) > numOfProducts) {
                    quantity = numOfProducts - totalamountSelected;
                    //Αν μπήκες εδώ σπάσε το for
                    numOfProducts = i; //για βγεις απο το for loop
                }
                //Ενημέρωσε την συνολική επιλεγμένη ποσότητα
                totalamountSelected += quantity;

                // Ελεξε αν η επιλεγμένη ποσότητα είναι >0
                if (quantity > 0) {

                    //Βάλε το προιόν στο καλάθι και την ΒΔ, με την ποσοτητά του
                    ProductPurchase pp = this.db.newProductPurchase(p, purchaseID, quantity);

                    // Ενημέρωσε και την κλάση Purchase kai Σώσε την αλαγή στην ΒΔ.
                    purchaseID.getProductPurchaseList().add(pp);
                    this.db.updatePurchase(purchaseID);

                    //και στο καλάθι
                    basket.add(pp);
                }
            }
        }
        return basket;
    }

    private void removePurchase() {
        // ξεκόλα την αγορά απο τον πελάτη

        this.customer.getPurchaseList().remove(this.purchase);
        this.db.updateCustomer(customer);


        // ξεκόλα την αγορά απο το κατάστημα
        this.store.getPurchaseList().remove(this.purchase);
        this.db.updateStore(store);

        //διέγραψε την αγορά στη ΒΔ
        this.db.deletePurchase(this.purchase);

    }

    public boolean isSubmitted() {
        return this.submitted;
    }

    public int getRetries() {
        return retries;
    }

    public Purchase getThreadPurchase() {
        return purchase;
    }

    public ArrayList<Calendar> getRetriesTimestamps() {
        return retriesTimestamps;
    }

    public boolean isValidCreditCard() {
        return validCreditCard;
    }

    @Override
    public void run() {
        try {
            Random r = new Random();
            this.submitted = false;
            retries = 0;

            // Σώσε την ώρα που ξεκίνησες
            long startTime = System.currentTimeMillis();


            do {
                // Δέσμευσε το lock του ThreadManager
                synchronized (tm) {
                    // Σώσε την ώρα της προσπάθειας
                    retriesTimestamps.add(Calendar.getInstance());
                    // Ελεξε αν έχουν γίνει εχπιρε τα 10 δευτερόλεπτα
                    long currentTime = System.currentTimeMillis();
                    long execTime = currentTime - startTime;
                    // Αν ο χρόνος εκτέλεσης είναι >10sec φύγε
                    if (execTime > 10000) {
                        //  System.out.println(this.getName() + " .Not Paeesed EXPIRE TIMER, " + retries + ", " + tm.hasAvailablePositions() + " " + this.purchase.getCustomer().getLastName());
                        break;
                    }

                    // Αν υπάρχουν διαθέσιμες θέσεις για την Αρχή Πιστωτικών Καρτών
                    if (tm.hasAvailablePositions()) {
                        // Τότε μείωσε τον αριθμό των διαθέσιμων θέσεων
                        tm.reduceAvailablePositions();
                        //   System.out.println(this.getName() + " .passed, " + retries + ", " + tm.hasAvailablePositions() + " " + this.purchase.getCustomer().getLastName());
                    } else {
                        // Αύξησε τον αριθμό των προσπαθειών κατά 1
                        retries++;


                        // Αν είναι η τρίτη προσπάθεια σταμάτα, to exases αλλιώς περίμενε
                        if (retries == 3) {
                            //      System.out.println(this.getName() + " .Not passed EXPIRE, " + retries + ", " + tm.hasAvailablePositions() + " " + this.purchase.getCustomer().getLastName());

                            break;
                        } else {
                            // περίμενε 1-10 δευτερόλεπτα
                            // System.out.println(this.getName() + " .Not passed WAIT, " + retries + ", " + tm.hasAvailablePositions() + " " + this.purchase.getCustomer().getLastName());
                            int sleepTime = (1 + r.nextInt(9)) * 1000;
                            tm.wait(sleepTime);
                            // ξαναπροσπάθησε μόλις γίνει expire ο timer
                            continue;
                        }
                    }
                }


                // ==== Ξεκίνησε την Συναλαγή
                // Επικοινωνία με την Αρχή Πιστωτικών Καρτών
                if (!ccverifier.verifyCard(customer.getCreditCard())) {
                    // Αν η κάρτα δεν υπάρχει η αγορά ακυρώνεται
                    System.out.println("Credit card of customer " + customer.getFirstName() + " " + customer.getLastName() + " is invalid");
                    validCreditCard = false;
                    break;
                }

                // Αφού ταυτοποιήθηκε η πιστωτική κάρτα θεωρώ ότι η συναλλαγή ολοκληρώθηκε επιτυχώς
                this.submitted = true;

                // Δέσμευσε το lock του ThreadManager
                synchronized (tm) {
                    // Αύξησε τον αριθμό των διαθέσιμων θέσεων
                    tm.increaseAvailablePositions();
                }


                //  Σώσε την ώρα του τέλους της συναλλαγής
                long endTime = System.currentTimeMillis();
                // Συνολικός χρόνος εκτέλεσης της συναλλαγής
                long execTime = endTime - startTime;
                // Αν ο χρόνος εκτέλεσης είναι <10sec περίμενε
                if (execTime < 10000) {
                    Thread.sleep(10000 - execTime);
                    //   System.out.println(this.getName() + " .passed WAITSUCC, " + retries + ", " + tm.hasAvailablePositions() + " " + this.purchase.getCustomer().getLastName());

                }



            } while (!this.submitted && retries < 4);

            //
            if (retries == 0) {
                retries = 1;
            }
            // Αν ο έλεγχος των στοιχείων της πιστωτικής κάρτας δεν ήταν επιτυχής,
            // τότε η αγορά θα πρέπει σβηστεί απο την ΒΔ
            // Δέσμευσε το lock του DBManager
            synchronized (db) {
                if (this.submitted != true) {
                    // Κάλεσε την μέθοδο submitPurchases
                    this.removePurchase();
                } else {
                    // υπολόγισε και ενημέρωσε τον πελάτη για επιταγές/πόντους
                    this.updateCustomerPointsAndChecks(this.customer, this.purchase);
                }

            }
        } catch (InterruptedException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }

    }
}
